package com.opages.mvc.adapter.common.condition;

import com.opages.mvc.adapter.common.condition.core.ConditionEvaluationReport;
import com.opages.mvc.adapter.common.condition.core.ConditionOutcome;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.expression.spel.SpelParseException;

/**
 * 注入条件注解通用父类
 * @author daizhong.liu
 */
public abstract class AnnotationCondition implements Condition {

	private final Log logger = LogFactory.getLog(getClass());

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}catch (NoClassDefFoundError ex) {
			throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
					+ ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on "
					+ "that class. This can also happen if you are "
					+ "@ComponentScanning a springframework package (e.g. if you "
					+ "put a @ComponentScan in the default package by mistake)", ex);
		}catch(SpelParseException ex) {
			throw new IllegalStateException("请检查Spel表达式是否有问题，或者通符${}内容是否在配置文件中存在！",ex);
		}catch (RuntimeException ex) {
			throw new IllegalStateException(classOrMethodName+"中@ConditionalOnExpression存在问题，请检查Spel表达式是否有问题，或者${}内容是否存在配置文件中！",ex);
		}
	}

//	private String getName(AnnotatedTypeMetadata metadata) {
//		if (metadata instanceof AnnotationMetadata) {
//			return ((AnnotationMetadata) metadata).getClassName();
//		}
//		if (metadata instanceof MethodMetadata) {
//			MethodMetadata methodMetadata = (MethodMetadata) metadata;
//			return methodMetadata.getDeclaringClassName() + "." + methodMetadata.getMethodName();
//		}
//		return metadata.toString();
//	}

	private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
		if (metadata instanceof ClassMetadata) {
			ClassMetadata classMetadata = (ClassMetadata) metadata;
			return classMetadata.getClassName();
		}
		MethodMetadata methodMetadata = (MethodMetadata) metadata;
		return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
	}

	private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
		if (context.getBeanFactory() != null) {
			ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this,outcome);
		}
	}

	/**
	 * Determine the outcome of the match along with suitable log output.
	 * @param context the condition context
	 * @param metadata the annotation metadata
	 * @return the condition outcome
	 */
	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

	/**
	 * Return true if any of the specified conditions match.
	 * @param context the context
	 * @param metadata the annotation meta-data
	 * @param conditions conditions to test
	 * @return {@code true} if any condition matches.
	 */
	protected final boolean anyMatches(ConditionContext context, AnnotatedTypeMetadata metadata,
			Condition... conditions) {
		for (Condition condition : conditions) {
			if (matches(context, metadata, condition)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Return true if any of the specified condition matches.
	 * @param context the context
	 * @param metadata the annotation meta-data
	 * @param condition condition to test
	 * @return {@code true} if the condition matches.
	 */
	protected final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) {
		if (condition instanceof AnnotationCondition) {
			return ((AnnotationCondition) condition).getMatchOutcome(context, metadata).isMatch();
		}
		return condition.matches(context, metadata);
	}

}
